using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using LiquidFireArts.Wormhole.Misc;

namespace Unibot
{
    public enum ElementType
    {
        Player,
        Enemy,
        Platform,
        Wall,
        Bullet,
        Item,
        Checkpoint,
        Friendly,
        Goal
    }


    public class PhysicalState
    {
        public Vector2 position { get; set; }
        public Vector2 velocity { get; set; } // should probably clamp this for terminal velocity
        public Vector2 acceleration { get; set; }
        public float friction_coeffecient { get; set; }
        public float elastic_coefficient { get; set; }
        public float rotation { get; set; }
        public float scale { get; set; }
    }

    public abstract class Element
    {
        public ElementType type { get; set; }
        public List<Shape> TypeA; // general purpose hitboxes
        public List<Shape> TypeB; // special situation hitboxes
        public Texture2D texture { get; set; } // texture, get and set for animation
        public PhysicalState physical_state;
        public List<Animation> animations;

        public abstract void onCollisionWith(Element other, GameTime gt); // overload this
        public abstract void Draw(SpriteBatch sb); // overload this
        public abstract void update(GameTime gt); // overload this

        public void physicalResolution(Element other, GameTime gt)
        {
            // collision resolution:
            Shape cShape = this.TypeA[0];
            Shape otherShape = other.TypeA[0];

            Vector2 cCentre = this.TypeA[0].Center;
            Vector2 contact = otherShape.GetNearestPointOnEdge(cCentre);

            // collision normal of second body w.r.t. first
            Vector2 c1 = Vector2.Normalize(contact - cCentre); // will be screwed if we try to normalize (0,0), not to be confused with perpendicular normal...

            // keep moving it's position until it no longer collides
            while (CollisionDetector2D.ShapeShapeIntersecting(cShape, otherShape))
            {
                this.physical_state.position -= 0.2f * c1; // move this.Element in the opposite direction of the collision normal
                cShape.Position -= 0.2f * c1; // and move it's shape
            }

            //Vector3 normal3D = Vector3.Cross(new Vector3(c1, 0), new Vector3(0, 0, -1));
            //Vector2 normal = new Vector2(normal3D.X, normal3D.Y);

            // reflect the velocity about the collision normal
            Vector2 newVelocity = Vector2.Reflect(this.physical_state.velocity, c1);

            // friction and elasticity... I'm less sure how to do them, so I've got a placeholder in there for friction and possibly incorrect Physics musings
            // multiply newVelocity's c1 component by cumulativeFrictionalCoefficient
            //float cumulativeElasticCoefficient = this.physical_state.elastic_coefficient * other.physical_state.elastic_coefficient;
            //newVelocity = cumulativeElasticCoefficient * newVelocity;
            // friction acts along the normal of c1 (ONLY) (aka backwards on the component of the edge against which we're rubbing)
            //float cumulativeFrictionalCoefficient = this.physical_state.friction_coeffecient * other.physical_state.friction_coeffecient;
            //newVelocity = cumulativeFrictionalCoefficient * newVelocity

            this.physical_state.velocity = newVelocity;
        }

        public void updateState(GameTime gt)
        {
            // update state:
            this.physical_state.position += this.physical_state.velocity;
            this.physical_state.velocity += this.physical_state.acceleration;
            // state has been consumed, leaving:
            this.physical_state.velocity = new Vector2(0.95f * this.physical_state.velocity.X, 0.95f * this.physical_state.velocity.Y); // replace with friction later on
            this.physical_state.acceleration = new Vector2(); // zero the acceleration
            updateHitboxes();
        }
        public void updateHitboxes()
        {
            foreach (Shape s in TypeA)
            {
                s.Position = this.physical_state.position;
            }
            foreach (Shape s in TypeB)
            {
                s.Position = this.physical_state.position;
            }
        }
    }
}
